#ifndef VERTEXS_H_
#define VERTEXS_H_

#include <GL/glut.h>
#include <vector>
#include <cassert>

#include "coord.h"

class Vertexs {
   private:
      typedef std::pair<GLenum, size_t> Grupo;
      std::vector<Grupo> grupos;
      std::vector<float> vertexArray;

      bool estaFinalizado;

   public:
      /*
       * Representa un conjunto de Grupos de vertexs. 
       * Cada vertex esta compuesto por:
       *    2 coordenadas de textura
       *    3 coordenadas de normales
       *    3 coordenadas de posiciones
       *
       * Cada Grupo de vertexs agrupa a los vertexs que seran renderizados 
       * simultaneamente con un mismo modo.
       *
       * Ejemplo de uso, renderizado de un cuadrado y un triangulo:
       *
       *    //Primero definimos los grupos y sus vertexs.
       *    //Esto puede realizarse una sola vez.
       *    //O bien puede ser cambiado los parametros en tiempo de ejecucion
       *    Coord normal(0, 0, 1)
       *    Vertexs v;
       * 
       *    v.grupo(GL_QUADS);
       *    v.vertex(0, 0, normal, Coord(0, 0, 0));
       *    v.vertex(1, 0, normal, Coord(1, 0, 0));
       *    v.vertex(1, 1, normal, Coord(1, 1, 0));
       *    v.vertex(0, 1, normal, Coord(0, 1, 0));
       *
       *    v.grupo(GL_TRIANGLES);
       *    v.vertex(0, 0, normal, Coord(0, 0, 0));
       *    v.vertex(0, 0, normal, Coord(1, 0, 0));
       *    v.vertex(1, 1, normal, Coord(1, 1, 0));
       *
       *    v.cerrar()
       *
       *    //Renderizado
       *    v.enlazar()
       *    v.dibujar()
       *
       * */
      Vertexs() : estaFinalizado(false) {}

      /*
       * Agrega un nuevo grupo de vertexs asociados al modo de renderizado pasado
       * como parametro.
       *
       * Tipicamente el uso de este metodo equivale al glBegin de OpenGL.
       * */
      void grupo(GLenum modo) {
         assert(not estaFinalizado);
         grupos.push_back(Grupo(modo, vertexArray.size() / 8));
      }

      /*
       * Vease la documentacion del constructor
       *
       * Equivale a glTexCoord, glNormal y glVertex
       * */
      void vertex(float tex_s, float tex_t, const Coord &normal, const Coord &posicion) {
         assert(not estaFinalizado);
         vertexArray.push_back(tex_s);
         vertexArray.push_back(tex_t);

         vertexArray.push_back(normal.x());
         vertexArray.push_back(normal.y());
         vertexArray.push_back(normal.z());

         vertexArray.push_back(posicion.x());
         vertexArray.push_back(posicion.y());
         vertexArray.push_back(posicion.z());
      }

      /*
       * Una vez creado e inicializado la instancia Vertex, es necesario
       * cerrarla antes de dibujarla.
       * */
      void cerrar() {
         assert(not estaFinalizado);
         grupo(GL_POINT); //Un grupo dummy para simplificar el algoritmo de dibujado
 
         estaFinalizado = true;
      }

      /*
       * Enlaza la instancia para su posterior renderizado.
       * La instancia debe estar cerrada (vease el metodo Vertexs::cerrar)
       *
       * A diferencia de otros casos, no existe un metodo "desenlazar()"
       * */
      void enlazar() const {
         assert(estaFinalizado);
         glInterleavedArrays(GL_T2F_N3F_V3F, 0, &vertexArray[0]);
      }

      /*
       * Renderiza los Vertexs. La instancia debe estar cerrada y enlazada.
       * */
      void dibujar() const {
         assert(estaFinalizado);

         std::vector<Grupo>::const_iterator it = grupos.begin();
         std::vector<Grupo>::const_iterator end = grupos.end();

         std::vector<Grupo>::const_iterator sig = grupos.begin();
         ++sig;

         for(; sig != end; ++it, ++sig) {
            glDrawArrays(it->first, it->second, (sig->second - it->second));
         }
      }

};

#endif
